'''
Created on Nov 22, 2009
This is the code for an in-progress undergraduate research project of Kevin Stueve
under the direction of William Stein at the University of Washington.
@author: kstueve


Outline/TODO/brainstorm/wishlist:

Optimized data structures for collision detection (from Baraff and others)
TODO: explain difference between collisions and contact
Friction can come into play during collisions and contact
TODO: talk about static/kinetic friction
Static friction is a conservative force
Kinetic friction is a nonconservative force
collision detection/response code
  When we detect a collision, use binary search,
  or regula falsa to back up to point right before collision
  Then we apply collision response code and restart simulation

After performing collision detection/response, we look for contact, 
 and apply contact forces.  This involves (difficult to implement) algorithms
 described by Baraff (CMU).
 
It might be nice to have the option of logging all calculations.

Need a GUI (options: Java, JavaScript)
Option for real-time interaction: 
  -"rubberbands" in the style of Erik Neuman (springs)
  -inverse kinematics, i.e. you change the position of a body by dragging it
    (I call this a hard constraint).
    It is important to deal with inconsistent hard constraints gracefully
  - springs that have a maximum force or power output (possibly more realistic and plausible)
    (I believe such systems could be solved using the methods Baraff used for friction)
    I might call these soft constraints
Zooming and grid, lock-to-grid option.  Should objects be drawn roughly when viewed from afar?
translating/ rotating camera?

Bodies
  -Need a way to represent polygons, circles, etc (maybe splines)
  -Allow different coeficients of friction, coeficients of restitution, etc
  -Drawing bodies using pen
  -Adding, subtracting, taking intersection of bodies
  -Motors
    -rotational
    -translational
    Could specify translational or rotational velocity as hard or soft constraints
  planes- function as ramps, walls, and floors
  pulleys-require ideal string to be simulated.  That is, infinitely stiff springs.
    string could be simulated using a system of many springs, but it wolud be much
    better to devote time to developing an algorithm that simulates strings elegantly
  springs-linear springs have a spring constant (stiffness) k and a damping constant,
    as well as a rest length
    Springs forces are conservative forces, but damping is not.
  anchoring bodies, making hinges and pivots
    
Light/Optics
  Lenses, mirrors, light rays, white light, laser light, wavelength, index of refraction,
  rainbows
  
Water/Gas
  Can be very roughly simulated using many disks.
  
Saving/Specification language

Declaring a variable unknown (I really want to be able to implement this)
  Sweeping- trying many values of the variable
  Numerical Solving- using binary search or a related decrease and conquer method to numerically solve
    for a value (such as time, angle, mass, coefficient of restitution etc)
  Symbolic Solving (time allowing)- either using an off-the-shelf (interactive) theorem prover
    or a new one (very difficult)
'''

from threading import Lock
import Queue
import subprocess
import threading

class PyPhysSim:
    '''
    classdocs
    '''


    def __init__(self):
        '''
        Constructor
        '''
        self.my_bodies={}
        
        #self.quit being set to true causes main loop to terminate
        self.quit=False
        
        self.pauseorplay="Pause"
        
        self.default_color="0 0 255"#red
        
        self.drawing_color="0 255 255"#cyan
        
        #self.drawingstate is used to keep track of whether we are in the middle of creating a new shape
        self.drawingstate="None"
        
        #self.nextshapeid holds the id that will be used for the next created shape
        self.nextshapeid=0
        
        #self.mode will be set by Java (Move, Create etc)
        #self.shape will be set by Java (Square, Rectangle, etc)
        
        #I used http://effbot.org/zone/thread-synchronization.htm as a resource about locks
        #self.lock is used so that only one of Java and Python can access the Java to Python message queue at once
        self.lock = Lock()
        
        #I used http://docs.python.org/library/queue.html#Queue.Queue as a resource about queues
        #self.messageQueue holds messages from Java to Python
        self.messageQueue=Queue.Queue()
    def start_java(self):
        #I used http://www.devshed.com/c/a/Python/Basic-Threading-in-Python/1/ as a resource about threads
        class MyThread ( threading.Thread):
            def __init__(self,mypps):
                threading.Thread.__init__(self)
                self.mypps=mypps
            def run ( self ):
                self.mypps.thesubprocess=subprocess.Popen(["java","edu.washington.math.sage.PyPhysSimGui"],
                                   cwd = "/home/kstueve/workspace/PyPhysSimGui/bin",
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE)
                while True:
                    message= self.mypps.thesubprocess.stdout.readline()[:-1]
                    if message=="" or message=="WINDOW_CLOSED":
                        self.mypps.quit=True
                        break
                    self.mypps.lock.acquire()
                    self.mypps.messageQueue.put(message)
                    self.mypps.lock.release()
                self.mypps.thesubprocess.wait()
        MyThread(self).start()
    def calculateframe(self,delay):
        if self.pauseorplay=="Play":
            for thebody in self.my_bodies:
                self.thesubprocess.stdin.write("TRANSLATE_SHAPE "+str(thebody)+" 1 0 0.01 0 0\nREPAINT\n")
            
    def start_main_loop(self):
        #I used http://stackoverflow.com/questions/2660919/python-animation-timing as a resource on Timing
        import time
        FPS = 20#TODO:magic number
        last_time = time.time()
        while not self.quit:
            # draw animation
            self.lock.acquire()
            while not self.messageQueue.empty():
                message=self.messageQueue.get().split()
                if message[0]=="MOUSE_CLICKED":
                    self.handleClick(message)
                elif message[0]=="MOUSE_PRESSED":
                    self.handlePress(message)
                elif message[0]=="MOUSE_RELEASED":
                    self.handleRelease(message)
                elif message[0]=="MOUSE_MOVED":
                    self.handleMove(message)
                elif message[0]=="MOUSE_DRAGGED":
                    self.handleDrag(message)
                elif message[0]=="MOUSE_ENTERED":
                    self.handleEnter(message)
                elif message[0]=="MOUSE_EXITED":
                    self.handleExit(message)
                elif message[0]=="BUTTON_PRESSED":
                    self.handleButton(message)
            self.lock.release()
            self.calculateframe(1000.0 / FPS)
            # pause so that the animation runs at 20 fps
            new_time = time.time()
            # see how many milliseconds we have to sleep for
            # then divide by 1000.0 since time.sleep() uses seconds
            sleep_time = ((1000.0 / FPS) - (new_time - last_time)) / 1000.0
            if sleep_time > 0:
                time.sleep(sleep_time)
            last_time = new_time
    def handleClick(self,message):
        #self.thesubprocess.stdin.write("CREATE_SHAPE awesomeshape 200 211 0 M 10 10 L 10 100 L 100 100 L 100 10 Z\n")
        #self.thesubprocess.stdin.write("TRANSLATE_SHAPE awesomeshape 50 -32 1 0 0\n")
    
        #self.thesubprocess.stdin.write("REPAINT\n")
        print message
    def handlePress(self,message):
        if self.mode=="Create":
            if message[3]=="1":#TODO: only allow certain positions
                self.drawingstate="Drawing"
                self.pressedx,self.pressedy=int(message[1]),int(message[2])
                self.tempshape=self.make_shape(self.shape,self.pressedx,self.pressedy)

        print message
    def handleRelease(self,message):
        if self.mode=="Create":
            if message[3]=="1":#TODO: only allow certain positions
                if self.drawingstate=="Drawing":
                    self.thesubprocess.stdin.write("DELETE_SHAPE tempshape\n")
                    if self.tempshape.diagonalsquared(self.mousex,self.mousey)>=100:
                        self.thesubprocess.stdin.write("CREATE_SHAPE "+str(self.nextshapeid)+" "+self.default_color+" "
                                          +self.tempshape.shapestring(self.mousex,self.mousey))
                        self.my_bodies[self.nextshapeid]=self.tempshape
                        self.nextshapeid+=1
                    self.thesubprocess.stdin.write("REPAINT\n")
                    self.drawingstate="None"
        print message
    def handleMove(self,message):
        print message
        self.mousex,self.mousey=int(message[1]),int(message[2])
    def handleDrag(self,message):
        self.mousex,self.mousey=int(message[1]),int(message[2])   
        if self.mode=="Create":
            if self.drawingstate=="Drawing":
                self.thesubprocess.stdin.write("CREATE_SHAPE tempshape "+self.drawing_color+" "
                                          +self.tempshape.shapestring(int(message[1]),int(message[2])))
                print self.tempshape.shapestring(int(message[1]),int(message[2]))
                self.tempshape.draggedx=int(message[1])
                self.tempshape.draggedy=int(message[2])
                #self.tempshape.adjustAnchor(0,0);
                self.thesubprocess.stdin.write("REPAINT\n")
        print message
    def handleEnter(self,message):
        self.mousex,self.mousey=int(message[1]),int(message[2])
        print message
    def handleExit(self,message):
        self.mousex,self.mousey=int(message[1]),int(message[2])
        print message
    def handleButton(self,message):
        if message[1] in ["Move","Create","Delete","Examine"]:
            self.mode=message[1]
            if self.mode!="Create" and self.drawingstate=="Drawing":
                self.drawingState="None"
        elif message[1] in ["Pause","Play"]:
            self.pauseorplay=message[1]
        elif message[1] in ["Square","Rectangle","Circle","Ellipse","Polygon","ReuleauxTriangle"]:
            self.shape=message[1]
        print message
    def make_shape(self,shape,pressedx,pressedy):
        if shape=="Rectangle":
            return Rectangle(pressedx=pressedx,pressedy=pressedy)
        elif shape=="Square":
            return Square(pressedx=pressedx,pressedy=pressedy)
        elif shape=="Circle":
            return Circle(pressedx=pressedx,pressedy=pressedy)
        elif shape=="Ellipse":
            return Ellipse(pressedx=pressedx,pressedy=pressedy)

class Body:
    '''
    classdocs
    velocity:
      translational: vx,vy
      rotational: omega
    position:
      translational: x,y
      rotational: theta
    accellerations calculated from forces
      -numerically integrated to obtain changes in velocity and position
    inertia:
      -tranlational inertia (mass)
      -rotational inertia (moment of inertia)
    location of center of mass
    '''
    def __init__(self,pressedx,pressedy):
        self.pressedx,self.pressedy=pressedx,pressedy
    def intersecting(self,otherBody):
        pass
    def inside(self,x,y):
        pass
    def relativevelocity(self,otherbody):
        pass
    
class Polygon(Body):
    pass
class Rectangle(Polygon):
    def diagonalsquared(self,x,y):
        return (x-self.pressedx)**2+(y-self.pressedy)**2
    def shapestring(self,x,y):
        return "M "+str(self.pressedx)+" "+str(self.pressedy)+" "\
             "L "+str(x)+" "+str(self.pressedy)+ " "\
              "L "+str(x)+" "+str(y)+" "\
              "L "+str(self.pressedx)+" "+str(y)+" "\
              "Z\n"
    #def adjustAnchor(self,x,y):
    #    self.anchorx=(self.pressedx+self.draggedx)/2
    #    self.anchorx=(self.pressedy+self.draggedy)/2
class Square(Rectangle):
    def diagonalsquared(self,x,y):
        width=abs(x-self.pressedx)
        height=abs(y-self.pressedy)
        if width<height:
            if y>self.pressedy:
                y=self.pressedy+width
            else:
                y=self.pressedy-width
        elif width>height:
            if x>self.pressedx:
                x=self.pressedx+height
            else:
                x=self.pressedx-height
        return (x-self.pressedx)**2+(y-self.pressedy)**2
    def shapestring(self,x,y):
        width=abs(x-self.pressedx)
        height=abs(y-self.pressedy)
        if width<height:
            if y>self.pressedy:
                y=self.pressedy+width
            else:
                y=self.pressedy-width
        elif width>height:
            if x>self.pressedx:
                x=self.pressedx+height
            else:
                x=self.pressedx-height
        return "M "+str(self.pressedx)+" "+str(self.pressedy)+" "\
             "L "+str(x)+" "+str(self.pressedy)+ " "\
              "L "+str(x)+" "+str(y)+" "\
              "L "+str(self.pressedx)+" "+str(y)+" "\
              "Z\n"
class Ellipse(Body):
    def diagonalsquared(self,x,y):
        return (x-self.pressedx)**2+(y-self.pressedy)**2
    def shapestring(self,x,y):
        magicnum=0.551784


        x0=(self.pressedx+x)/2.0
        y0=self.pressedy
        
        x1=x0+magicnum*(x-x0)
        y1=y0
        
        x3=x
        y3=(self.pressedy+y)/2.0
        
        x2=x
        y2=y3+magicnum*(self.pressedy-y3)
        
        move1="M "+str(x0)+" "+str(y0)+" "
        curve1="C "+str(x1)+" "+str(y1)+" "+str(x2)+" "+str(y2)+" "+str(x3)+" "+str(y3)+" "
        
        x0=x
        y0=(self.pressedy+y)/2.0
        
        x1=x
        y1=y0+magicnum*(y-y0)
        
        x3=(self.pressedx+x)/2.0
        y3=y
        
        x2=x3+magicnum*(x-x3)
        y2=y
        
        curve2="C "+str(x1)+" "+str(y1)+" "+str(x2)+" "+str(y2)+" "+str(x3)+" "+str(y3)+" "
        
        
        
        x0=(self.pressedx+x)/2.0
        y0=y
        
        x1=x0+magicnum*(self.pressedx-x0)
        y1=y0
        
        x3=self.pressedx
        y3=(self.pressedy+y)/2.0
        
        x2=self.pressedx
        y2=y3+magicnum*(y-y3)
        
        curve3="C "+str(x1)+" "+str(y1)+" "+str(x2)+" "+str(y2)+" "+str(x3)+" "+str(y3)+" "
        
        x0=self.pressedx
        y0=(self.pressedy+y)/2.0
        
        x1=self.pressedx
        y1=y0+magicnum*(self.pressedy-y0)
        
        x3=(self.pressedx+x)/2.0
        y3=self.pressedy
        
        x2=x3+magicnum*(self.pressedx-x3)
        y2=self.pressedy
        
        curve4="C "+str(x1)+" "+str(y1)+" "+str(x2)+" "+str(y2)+" "+str(x3)+" "+str(y3)+" "

        return move1+curve1+curve2+curve3+curve4+"Z\n"
class Circle(Ellipse):
    def diagonalsquared(self,x,y):
        width=abs(x-self.pressedx)
        height=abs(y-self.pressedy)
        if width<height:
            if y>self.pressedy:
                y=self.pressedy+width
            else:
                y=self.pressedy-width
        elif width>height:
            if x>self.pressedx:
                x=self.pressedx+height
            else:
                x=self.pressedx-height
        return (x-self.pressedx)**2+(y-self.pressedy)**2
    def shapestring(self,x,y):
        width=abs(x-self.pressedx)
        height=abs(y-self.pressedy)
        if width<height:
            if y>self.pressedy:
                y=self.pressedy+width
            else:
                y=self.pressedy-width
        elif width>height:
            if x>self.pressedx:
                x=self.pressedx+height
            else:
                x=self.pressedx-height
        return Ellipse.shapestring(self,x,y)

if __name__=="__main__":
    pps=PyPhysSim()
    pps.start_java()
    pps.start_main_loop()